package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.entity.Goods;

import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.range.AbstractRangeBuilder;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.additional.idlist.IdListMapper;

import java.util.*;
import java.util.stream.Collectors;


@Service
public class GoodsService {

    @Autowired
    private GoodsRepository repository;

    /*
     * @User Administrator
     * @Description //TODO 可以操作简单的索引库增删改查方法
     * @Date 14:25 2019/8/14 0014
     * @Param 
     * @return 
     **/
    @Autowired
    private GoodsService goodsService;

    /*
     * @User Administrator
     * @Description //TODO  可以操作复杂的索引库方法
     * @Date 14:26 2019/8/14 0014
     * @Param
     * @return
     **/
    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Autowired
    private ItemClient itemClient;

    public Goods createGoods(SpuDTO spuDTO) {
        //封装skus
        List<SkuDTO> skuDTOS = itemClient.querySkuBySpuId(spuDTO.getId());
        // 准备一个集合，只存放四个属性的sku
        List<Map<String, Object>> skuList = new ArrayList<>();
        /*
         * @User Administrator
         * @Description //TODO  skuDTOS.forEach(skuDTO->{  forEach  遍历并收集
         **/
        skuDTOS.forEach(skuDTO->{
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", skuDTO.getId());
            skuMap.put("title", skuDTO.getTitle());
            skuMap.put("price", skuDTO.getPrice());
            skuMap.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            skuList.add(skuMap);
        });


        //封装All
        String all = spuDTO.getName() + spuDTO.getBrandName() + spuDTO.getCategoryName();

        /*
         * @User Administrator
         * @Description //TODO //封装价格  skuDTOS.stream()   把集合变成流
         *                 .map(SkuDTO::getPrice)    map里面填什么就是要这个流中的什么值
         *                 .collect(Collectors.toSet())  把一堆零散的价格 收集成 set(集合)
         * @Date 14:53 2019/8/14 0014
         * @Param [spuDTO]
         * @return com.leyou.search.entity.Goods
         **/
        Set<Long> spaceSet = skuDTOS.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //封装规格参数
        Map<String, Object> spces = new HashMap<>();
            //得到规格参数 map中的key
        List<SpecParamDTO> paramDTOS = itemClient.querySpecParams(null, spuDTO.getCid3(), true);
            //得到规格参数 map 中的 value
        SpuDetailDTO spuDetailDTO = itemClient.querySpuDetailById(spuDTO.getId());
            //得到 通用 规格参数的值 (字符串)
        String genericSpec = spuDetailDTO.getGenericSpec();
                //把通用规格字符串转成map
        Map<Long, Object> genericMap = JsonUtils.toMap(genericSpec, long.class, Object.class);
            //得到 私有 规格参数的值 (字符串)
        String specialSpec = spuDetailDTO.getSpecialSpec();
                //把私有规格参数字符串转成map
        Map<Long, List<String>> specialMap = JsonUtils.nativeRead(specialSpec, new TypeReference<Map<Long, List<String>>>() {});
        //遍历规格参数得到最终需要的 key  和 value
        paramDTOS.forEach(Param->{
            String key = Param.getName();
            Object value = null;
            //如果是通用规格参数,从genericMap(通用规格 集合) 取value
            if (Param.getGeneric()) {
                value = genericMap.get(Param.getId());
            } else {
                value = specialMap.get(Param.getId());
            }
            //判断如果是数字类型
            if (Param.getNumeric()) {
                //不用自己写,替换索引库中所有数字类型的字段值为区间
                value = chooseSegment(value, Param);
            }
            spces.put(key,value);
        });

        //创建Goods对象
        Goods goods = new Goods();
        goods.setId(spuDTO.getId());
        goods.setBrandId(spuDTO.getBrandId());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setSubTitle(spuDTO.getSubTitle());

        goods.setPrice(spaceSet);
        goods.setSkus(JsonUtils.toString(skuList));
        goods.setSpecs(spces);
        goods.setAll(all);

        return goods;
    }
    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }


    public PageResult<GoodsDTO> search(SearchRequest request) {
        // 0.健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 1.创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.组织条件
        // 2.0.source过滤，控制字段数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        // 2.1.搜索条件
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        // 2.2.分页条件
        int page = request.getPage() - 1;
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));

        // 3.搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析结果
        // 4.1.解析分页数据
        long total = result.getTotalElements();
        int totalPage = result.getTotalPages();
        List<Goods> list = result.getContent();
        // 4.2.转换DTO
        List<GoodsDTO> dtoList = BeanHelper.copyWithCollection(list, GoodsDTO.class);

        // 5.封装并返回
        return new PageResult<>(total, totalPage, dtoList);

    }


    private QueryBuilder buildBasicQuery(SearchRequest request) {
        // 构建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        // 构建基本的match查询
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        // 构建过滤条件
        Map<String, String> filters = request.getFilters();
        if(!CollectionUtils.isEmpty(filters)) {
            for (Map.Entry<String, String> entry : filters.entrySet()) {
                // 获取过滤条件的key
                String key = entry.getKey();
                // 规格参数的key要做前缀specs.
                if ("分类".equals(key)) {
                    key = "categoryId";
                } else if ("品牌".equals(key)) {
                    key = "brandId";
                } else {
                    key = "specs." + key;
                }
                // value
                String value = entry.getValue();
                // 添加过滤条件
                queryBuilder.filter(QueryBuilders.termQuery(key, value));
            }
        }

        return queryBuilder;
    }

    /*
     * @User Administrator
     * @Description //TODO 查询过滤项
     * @return
     **/

    public Map<String, List<?>> queryFilters(SearchRequest request) {
        // 1.创建过滤项集合
        Map<String, List<?>> filterList = new LinkedHashMap<>();

        // 2.查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.1.获取查询条件
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        // 2.2.减少查询结果(这里只需要聚合结果)
        // 每页显示1个
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 显示空的source
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        // 3.聚合条件
        // 3.1.分类聚合
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        // 3.2.品牌聚合
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));

        // 4.查询并解析结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();
        // 4.1.获取分类聚合
        LongTerms cTerms = aggregations.get(categoryAgg);
        List<Long> idList = handleCategoryAgg(cTerms, filterList);
        // 4.2.获取分类聚合
        LongTerms bTerms = aggregations.get(brandAgg);
        handleBrandAgg(bTerms, filterList);

        // 5.规格参数处理
        if (idList != null && idList.size() == 1) {
            // 处理规格，需要参数：分类的id，查询条件，过滤项集合
            handleSpecAgg(idList.get(0), basicQuery, filterList);
        }
        return filterList;
    }

    private void handleSpecAgg(Long cid, QueryBuilder basicQuery, Map<String, List<?>> filterList) {
        //根据分类查询出所有需要过滤的规格参数
        List<SpecParamDTO> paramDTOS = itemClient.querySpecParams(null, cid, true);
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //设置不查询任何字段  可以不写
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        //封装查询条件【搜索框中的key】必须写
        queryBuilder.withQuery(basicQuery);
        //设置查询的数量  可以不写
        queryBuilder.withPageable(PageRequest.of(0,1));
        //变量当前分类下所有的规格参数进行聚合条件的封装
        paramDTOS.forEach(param->{
            String aggName = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(aggName).field("specs."+aggName));
        });
        //聚合查询
        AggregatedPage<Goods> goodsRrsult = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //得到所有的聚合结果
        Aggregations aggregations = goodsRrsult.getAggregations();
        //循环，解析聚合结果
        paramDTOS.forEach(param->{
            String aggName = param.getName();
            Terms terms = aggregations.get(aggName);
            List<String> paramsValue = terms.getBuckets().stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());
            filterList.put(aggName, paramsValue);
        });
    }

    private void handleBrandAgg(LongTerms terms, Map<String, List<?>> filterList) {
        // 解析bucket，得到id集合
        List<Long> idList = terms.getBuckets().stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        // 根据id集合查询品牌
        List<BrandDTO> brandList = itemClient.queryBrandByIds(idList);
        // 存入map
        filterList.put("品牌", brandList);
    }

    private List<Long> handleCategoryAgg(LongTerms terms, Map<String, List<?>> filterList) {
        // 解析bucket，得到id集合
        List<Long> idList = terms.getBuckets().stream()
                .map(LongTerms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        // 根据id集合查询分类
        List<CategoryDTO> categoryList = itemClient.findByBrandIds(idList);
        // 存入map
        filterList.put("分类", categoryList);
        return idList;
    }

    public void createIndex(Long id){
        // 查询spu
        SpuDTO spu = itemClient.querySpuById(id);
        // 构建成goods对象
        Goods goods = createGoods(spu);
        // 保存数据到索引库
        repository.save(goods);
    }

    public void deleteById(Long id) {
        repository.deleteById(id);
    }
}

